Add dynamically generated event invoice with PDF download#1709
Conversation
| payer = submission.person | ||
| payer_name = [ answers["payer_first_name"], answers["payer_last_name"] ] | ||
| .map(&:presence).compact.join(" ").presence || payer&.full_name | ||
| quantity = [ submission.bulk_payment_attendee_count, 1 ].max |
There was a problem hiding this comment.
🤖 From Claude: Bulk invoices bill the payer's org for every attendee submitted (qty × event cost), so an 8-person submission for a $1,500 event totals $12,000 — matching the source template. Falls back to qty 1 if the count is missing/zero.
| def show | ||
| authorize! @event, to: :invoice? | ||
|
|
||
| if params[:submission_id].present? |
There was a problem hiding this comment.
🤖 From Claude: One admin route serves both cases: no submission_id → blank template with just the event content; with one → autofilled from the bulk-payment submission. This is why there's no dedicated bulk-payment invoice route.
| end | ||
| end | ||
|
|
||
| def invoice |
There was a problem hiding this comment.
🤖 From Claude: Public invoice authorized via show_public? (the secret slug is the credential), consistent with the registration ticket page right below it. The admin /events/:id/invoice route is the manage-gated counterpart.
| def show_invoice? | ||
| record.role == "bulk_payment" || admin? | ||
| end | ||
| end |
There was a problem hiding this comment.
🤖 From Claude: Bulk-payment submissions are already publicly viewable by id (the payer has no account and is emailed the link), so their invoice follows the same rule. Other submission roles (registration/scholarship) stay admin-only.
| # autofills the bill-to/attention from that bulk-payment submission. | ||
| class InvoicesController < ApplicationController | ||
| # Bulk-payment payers have no account; authorization (below) gates access. | ||
| skip_before_action :authenticate_user!, only: [ :show ] |
There was a problem hiding this comment.
🤖 From Claude: Auth is skipped here (like the bulk-payment controller's public actions) and access is enforced by the per-branch authorize! calls below: public show_invoice? for a bulk submission, admin invoice? for the blank template.
AWBW staff and registrants need a printable invoice for paid events. Rather than maintaining a static template per event, this generates the invoice from live data — the brand logo from the asset pipeline, the recipient from the registration/payer, and the line item from the event. One EventInvoice presenter normalizes three sources into a single printable layout: a per-registration invoice (public, reached via the registration's secret slug), and an admin-side event invoice that renders a blank template prefilled with the event's content, autofilling the bill-to/attention from a bulk-payment submission when submission_id is present. PDF export reuses the app's existing print convention (print: Tailwind utilities + a small print Stimulus controller) so no PDF gem or binary is introduced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bulk-payment payers are emailed a link to their (public) submission page, so the invoice needs to be reachable from there — add a "View invoice" link on both the public bulk-payment show page and the admin form-submission page. A bulk-payment submission's invoice is now public (gated by FormSubmission show_invoice?), matching that the submission show page is already public by id; the blank template stays admin-only. Also drop the bespoke print Stimulus controller in favor of the inline onclick="window.print();" pattern already used by sibling event views (recipients, background, social share), and render the invoice on a neutral public background rather than the admin blue tint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Main (#1707) added an "Invoice"/"W-9" opt-in to public registration that sets invoice_requested/w9_requested, with the digital ticket surfacing those downloads — but only the W-9 download was wired up, since the invoice page didn't exist yet. Surface the invoice the same way: a card matching the W-9 one, shown when the registrant requested an invoice, completing that intent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53f04d6 to
160eb51
Compare
Mark two facilitator/trauma training registrations as having requested an invoice so the dev dataset exercises the ticket's "View invoice" surface, mirroring the existing scholarship_requested seed flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gives the dev dataset a registration that exercises both the W-9 and invoice ticket surfaces at once, and threads w9_requested through the seed create. Maria and Sarah keep their invoice-only flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new event/registration invoice views set content_for(:page_bg_class), which the alignment guard requires to be accounted for. Both are publicly reachable (slug / bulk-payment submission id), so "public" is the honest value; the blank-template admin gating is enforced in the controller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What is the goal of this PR and why is this important?
How did you approach the change?
EventInvoicepresenter (app/presenters/event_invoice.rb) that normalizes three sources into one printable layout:from_registration— one attendee billed at the event cost; the registrant's snapshotted organization (with address) is the Invoice To, falling back to the person.from_event— a blank template carrying only the event's content.from_bulk_payment— bills the payer's organization for every attendee submitted (qty × event cost).GET /events/:id/invoice→events/invoices#show. Renders the blank template (admin-only); autofills from a bulk-payment submission whensubmission_idis present, and that case is public (gated byFormSubmissionPolicy#show_invoice?) — matching that the bulk-payment submission show page is already public by id.GET /registration/:slug/invoice→events/registrations#invoice(public, the secret slug is the authorization).onclick="window.print();"(same as therecipients/background/social-share views), and the chrome isprint:hiddenso only the invoice document lands in the PDF. No PDF gem/binary and no new Stimulus controller.return_toso each origin gets the correct back link.UI Testing Checklist
/events/:id/invoice(blank) or a non-bulk submission's invoice is redirectedAnything else to add?
🤖 Generated with Claude Code